package tcln.sors.model.core.dao;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import tcln.commons.collections.ArrayUtils;
import tcln.commons.collections.CollectionUtils;
import tcln.commons.lang.StringUtils;
import tcln.sors.model.core.dom.Entity;
import ch.elca.el4j.services.persistence.hibernate.dao.GenericHibernateDao;

/**
 * Default implementation of {@link Dao} for Hibernate.
 * 
 * @param <E>
 *            Entity type.
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class DefaultHibernateDao<E extends Entity<Integer>> extends GenericHibernateDao<E, Integer> implements Dao<E> {

    /**
     * constructs this DAO whith a default order: sort ascendingly by entity ID.
     */
    public DefaultHibernateDao() {
        super();
        setDefaultOrder(Order.asc("id"));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DetachedCriteria createCriteria() {
        return DetachedCriteria.forClass(getPersistentClass());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public E findByIdWeak(Integer id) {
        E entity = null;
        if (id != null) {
            entity = getConvenienceHibernateTemplate().get(getPersistentClass(), id);
        }
        return entity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Dao<E> flushSession() {
        flush();
        getConvenienceHibernateTemplate().clear();
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteOthers(Integer... excludedEntityIds) {
        if (!ArrayUtils.isEmpty(excludedEntityIds)) {

            // build IN clause
            List<String> ids = new ArrayList<String>(excludedEntityIds.length);
            for (int i = 0; i < excludedEntityIds.length; i++) {
                ids.add("?");
            }
            String inClause = StringUtils.join(ids, ',');

            // execute query
            getConvenienceHibernateTemplate().bulkUpdate(
                    "DELETE FROM " + getPersistentClassName() + " WHERE id NOT IN (" + inClause + ")",
                    (Object[]) excludedEntityIds);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<E> findByIds(Integer... ids) {
        List<E> results = null;
        if (ArrayUtils.isEmpty(ids)) {
            results = Collections.emptyList();
        } else {
            DetachedCriteria criteria = createCriteria();
            criteria.add(Restrictions.in("id", ids));
            return findByCriteria(criteria);
        }
        return results;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<E> findByIds(Collection<Integer> ids) {
        return findByIds(ids.toArray(new Integer[ids.size()]));
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public <O> O max(String property) {
        DetachedCriteria criteria = createCriteria();
        criteria.setProjection(Projections.max(property));
        List<?> results = findByCriteria(criteria);
        if (results.isEmpty()) {
            return null;
        }
        return (O) results.get(0);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<E> saveOrUpdate(List<E> entities) {
        if (CollectionUtils.isEmpty(entities)) {
            return entities;
        }

        List<E> results = new ArrayList<E>(entities.size());
        for (E t : entities) {
            results.add(saveOrUpdate(t));
        }
        return results;
    }
    
    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public E merge(E entity) {
        if (entity.isPersisted() && !getSession().contains(entity)) {
            return (E) getSession().merge(entity);
        }
        return entity;
    }
}
